#! /usr/bin/python
import os, fnmatch, re, json
from element import Symbol, Element
from utility import getFileName, cmd


class Resource:
    """
    @param path
        The path to the resource
    @param name
        The name of the resource with complete namespace
    """
    def __init__(self, path, name):
        self.path = path
        self.name = name
    
    def __str__(self):
        return  '[%s] %s' % (self.path, self.name)


def resourceList(base, include_patterns, esclude_files=[]):
    """ 
    This is the most basic routine to generate a resource list to be used by a 
    ResourceParser
    @param base
        The base directory to be recursively scanned
    @param include_patterns
        The pattern list of the resources we want to add to the resource list
    @param esclude_files
        A list of files to be excluded
    @return
        The resource list
    """
    resourceList = []
    for root, _, files in os.walk(base):
        for file in files:
            if file in esclude_files:
                continue
            for pattern in include_patterns:
                if fnmatch.fnmatch(file, pattern):
                    path = os.path.join(root, file)
                    
                    namespace = root.replace(base, '').replace('\\', '.').replace('/', '.').strip('.')
                    if namespace != '':
                        name = namespace+'.'
                    else:
                        name = ''
                    name += getFileName(file)
                    
                    resourceList.append(Resource(path, name))
                    break
    return resourceList


def cache_file(resource):
    """
    The caching of resources is not very useful for single object files, but it 
    becomes really effective for libraries
    """
    return getFileName(resource.path) + '.json'


class ResourceParser:
    def gen_cache(self, resource, element_list):
        f = open(cache_file(resource), 'w')
        json.dump([e.json_serialize() for e in element_list], f)
    
    def clean_cache(self, resources):
        for resource in resources:
            cmd(('rm', '-f', cache_file(resource)))
    
    def get_cache(self, resource):
        """ Get the resource element_list cache
        @param resource
            The resource
        @return
            * An element_list loaded from the cache
            * None if there is no cache file for that resource, or if the resource was
            modified after the generation of the cache file.
        """
        cache = cache_file(resource)
        if not os.path.exists(cache):
            return None
        
        if os.path.getmtime(cache) < os.path.getmtime(resource.path):
            return None
        
        data = json.load(open(cache))
        elementList = []
        for element in data:
            e = Element(element["name"])
            e.provided_symbols = {}
            for s in element["provided_symbols"]:
                e.provided_symbols[s['name']] = Symbol(**s)
            e.used_symbols = {}
            for s in element["used_symbols"]:
                e.used_symbols[s['name']] = Symbol(**s)
            elementList.append(e)
        return elementList
    
    def parse_resources(self, resources, cache=False):
        element_list = []
        for resource in resources:
            print 'Processing: ' + resource.path
            if cache:
                elements = self.get_cache(resource)
                if elements is not None:
                    element_list += elements
                    continue
            
            elements = self.parse_resource(resource, cache)
            element_list += elements
            if cache:
                self.gen_cache(resource, elements)
        return element_list
        
    def parse_resource(self, resource, cache=False):
        raise NotImplementedError


class GNUParser(ResourceParser):
    """
    The GNU nm is the most spread object parser for symbols definition.
    For this reason we provide it as a reference implementation for a
    ResourceParser
    @param resourceList
        A list of resources to be parsed
    @return
        A list of elements containing the linker information, but not yet the dependencies information
    """
    
    # output the table of all the global defined symbols with demangled names
    NM = 'nm -g -C %s'
    PATTERN = re.compile('([0-9a-fA-F]+|\s+) ([ABbCDdGgiNpRrSsTtUuVvWw\-\?]) (.+)')
    
    def parse_resource(self, resource, cache=False):
        element_list = []
        elm = Element(resource.name)
        
        proc = os.popen(GNUParser.NM % resource.path, 'r')
        for line in proc:
            m = GNUParser.PATTERN.match(line)
            if m:
                t = m.group(2)
                name = m.group(3).strip()
                sym = Symbol(name, t)
                if sym.type in 'VW':
                    # Ignore weak symbols
                    continue
                elif sym.type == 'U':
                    elm.used_symbols[name] = sym
                else:
                    elm.provided_symbols[name] = sym
        
        # In this case we generate only one element for resource
        # If the resource is a library, a different implementation could return
        # a list of elements, one for each object inside the library
        element_list.append(elm)
        
        return element_list

